========================================= === Lode Runner NES === === Level Format === === v1.1 === === by Disch === ========================================= THE INTRODUCTION: ----------------------------------- This was a fun one to figure out. They must have been really tight on space -- considering this is only a 16K ROM. Anyway the compression is pretty intricate, and everything is bitpacked so that minimal space is wasted! Additionally, *THERE IS NO POINTER TABLE*. To work around this madness, the game will actually load every level up to the one you're on (this is probably why you have to wait so long while it's playing that jingle at the start of each level). Of course bitpacked and no pointers also means editing these levels will be totally impractical without a level editor. You would need to unpack all the bits, decompress your level, edit it, recompress it, then bitpack all the levels again. A lot of work. Anyway without further stalling: THE TILESET: ----------------------------------- There are 8 tiles: 0 = open space 1 = brick wall (diggable) 2 = solid wall (not diggable) 3 = ladder 4 = rope (that you hang on / shimmie across) 5 = fake floor (drop through) 6 = goal ladder (appears after you've collected enough gold) 7 = pile of gold THE DIMENSIONS / OFFSETS: ----------------------------------- - Maps consist of 14 rows - Each row consists of 28 tiles - Rows are stored sequentially, top row being stored first, bottom row stored last - tiles in each row are stored left to right (usually) Maps start at offset 0x02A88 (CPU address $EA78). The pointer to the start of map data is hardcoded -- if you want to change it (like, say, if you expand the ROM to add more space for levels), the high byte ($EA) is at offset 0x0072E and the low byte ($78) is at offset 0x0071A. THE BITPACKING: ----------------------------------- The game "pulls" 1, 2, 3, or 5 bit values from the level data at a time, starting with the high bit. It does not read full bytes of the level data. For example, given the following bytes: (hex): A2 67 12 You would have the following bitstring: (bin): 10100010 01100111 00010010 If the game pulled, in order, a 3 bit value, then a 2 bit value, then a 5 bit value, it'd get 5, 0, and 9 respectively: (bin): [101] [00] [01001] 100111 00010010 5 0 9 You *never* skip over the remaining bits and go to the next byte. EVERYTHING is bitpacked. There is no guarantee that any level (other than level 1) will start cleanly on a byte boundary. OVERALL MAP STORAGE: ----------------------------------- Each map consists of the following (stored in order): - 14 compression blocks, each compressing a single row - enemy data/positions (at least 1 enemy must exist per level) - player starting position Immediately following the player position is the start of the next level's map. ROW COMPRESSION BLOCKS: ----------------------------------- Each row is represented by a single compression block. The block starts with a 2-bit header that indicates which type of block it is. There are 4 different kinds of blocks. << Block 0 >> Block 0 is a basic form of RLE. It can contain any number of runs. 2-bit : header. Must be %00 to identify this block as block 0 1-bit : control. Set if there is more than 1 run {{ skip this section if control bit is clear }} 5-bit : length of run. Must not be 0 -- must not reach end of row 3-bit : tile to run 1-bit : control. Set if there is yet another run {{ repeat until control bit is clear }} 3-bit : tile to run. This tile makes up the rest of the row. Example: (hex): 26 12 92 (bin): 00100110 00010010 10010010 (bin): 00 1 00110 000 1 00101 001 0 010 00 = header (identifies this as block 0) 1 = control bit is set, so there is more than 1 run remaining 00110 = run length of 6 000 = tile ID of 0 ---> output tile 0 6 times 1 = control bit is set, so there is more than 1 run remaining 00101 = run length of 5 001 = tile ID of 1 ---> output tile 1 5 times 0 = control bit is clear, so only 1 run remaining 010 = tile ID of 2 ---> output tile 2 until end of row Decompresses to: 0000001111122222222222222222 << Block 1 >> Block 1 is a 1-bit palettized row. 2-bit : header. Must be %01 to identify this block as block 1 3-bit : palette ... tile 'A' 3-bit : palette ... tile 'B' {{ repeat this section 28 times -- once for each tile }} 1-bit : palette index of this tile {{ }} Example: (hex): 47 64 67 76 70 (bin): 01000111 01100100 01100111 01110110 0111xxxx (bin): 01 000 111 0110010001100111011101100111 01 = header (identifies this as block 1) 000 = tile A in palette 111 = tile B in palette the rest = 1 bit-per-tile "bitmap" of the row Decompresses to: 0770070007700777077707700777 << Block 2 >> Block 2 basically is like "this row is all tile 0, except for..." and then lists each tile individually. 2-bit : header. %10 for block 2 1-bit : control. Set if there are tiles that are nonzero {{ skip this section if control bit is clear }} 3-bit : tile ID 5-bit : tile X position (must be < $1C) 1-bit : control. Set if there are more tiles that are nonzero {{ repeat this section until control bit is clear Example: (hex): BA 32 20 (bin): 10111010 00110010 00100xxx (bin): 10 1 110 10001 1 001 00010 0 10 = header (identifies as block 2) 1 = control bit is set... there's at least 1 tile that's nonzero 110 = the tile ID 10001 = it's X position 1 = control bit. Set again, so there's another one 001 = tile ID 00010 = X position 0 = control bit. This time it's clear, so we're done Decompresses to: 0010000000000000060000000000 << Block 3 >> This is two blocks in one. One is an uncompressed block (but still bitpacked), and the other is a 2-bits per tile "bitmap" similar to Block 1. To switch between the two there's an additional (or secondary) header bit that comes after the normal 2-byte header: I won't give examples for either type, because Block 3B is totally straightforward and Block 3A is similar to Block 1 in concept. << Block 3A >> 2-bit : header. Must be %11 1-bit : secondary header. Must be 0 (if 1, use Block 3B instead) 3-bit : palette ... tile 'A' 3-bit : palette ... tile 'B' 3-bit : palette ... tile 'C' 3-bit : palette ... tile 'D' {{ repeat this section 28 times -- once for each tile }} 2-bit : palette index of this tile {{ }} << Block 3B >> This is the uncompressed version of block 3: 2-bit : header. Must be %11 1-bit : secondary header. Must be 1 (if 0, use Block 3A instead) {{ repeat this section 28 times -- once for each tile }} 3-bit : tile to output {{ }} ENEMY/PLAYER POSITIONS: ----------------------------------- Immediately following the row blocks is the enemy info: 3-bit : number of enemies (must not be zero) {{ for each enemy }} 5-bit : X position (must be < $1C) 5-bit : Y position (must be < $0E) {{ repeat for each enemy }} Note that even though Y position could be a 4-bit value because it can't be above $0D... the game actually reads a full 5-bit value... probably only because the game has no 4-bit reading routine. Following that is another pair of 5-bit values that specify the player's starting position. And of course, immediately following the player position is the start of the level data for the next level! Remember it's all bitpacked! That's it!